home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fatted Calf
/
The Fatted Calf.iso
/
Applications
/
Workspace
/
Briefcase
/
Source
/
MultApp.m
< prev
next >
Wrap
Text File
|
1992-08-10
|
25KB
|
889 lines
#import "MultApp.h"
#import "MultDoc.h"
#import "Help.h"
#import "PrefDelegate.h"
#import "PageMargin.h"
#import <appkit/appkit.h>
@implementation MultApp:Object
/* Canon Information Systems is not responsible for anything anyone does with this */
/* code, nor are they responsible for the correctness of this code. Basically, this */
/* has very little to do with the company I work for, and you can't blame them. */
/* This file is best read in a window as wide as this comment, and with tab settings */
/* of 4 spaces and indent setting of 4 spaces (at least, that's what I use). */
/* You are welcome to do as you would with this file under the following conditions. */
/* First, I accept no blame for anything that goes wrong no matter how you use it, */
/* no matter how catastrophic, not even if it stems from a bug in my code. */
/* Second, please keep my notices on it when/if you distribute it. */
/* Third, if you discover any bugs or have any comments, PLEASE TELL ME! Code won't */
/* get better without people picking it apart and giving the writer feedback. */
/* Fourth, if you modify it, please keep a notice that your version is based on mine */
/* in the source files (and keep the notice that mine is based on four other pieces */
/* of code :<). Thanks, and have fun. - Subrata Sircar, ssircar@canon.com */
/* This class is intended as an Application Delegate. As such it */
/* is supposed to provide the document-management functionality */
/* that a multiple-document, drag-and-drop application should be */
/* able to do. This includes keeping track of the current thing, */
/* managing the shared panels, updating the defaults and menus, */
/* and controlling communication with the Workspace Manager. */
/* The paradigm used is that the application delegate relays new messages */
/* to the factory Document class, and every other doc message goes to */
/* the first responder. The delegate opens nib sections containing */
/* the info and preferences panel (the prefs panel should have a delegate */
/* to update it) and lets the helpObject worry about itself. Otherwise */
/* it acts on delegate messages. */
/* Source code stolen from Draw, Acceptor, and WhatsUpDoc. Thanks! */
/* Source code also taken from Ernest Prabhakar's BasicApp example, which */
/* includes more methods than mine does (for things like services, etc.) */
/* His examples are well worth checking out, especially if you need more */
/* functionality than this class can provide. */
/* - Subrata Sircar ssircar@canon.com Canon Information Systems */
/* Version 0.9b Apr-19-92 First Public Release */
/* Version 0.95b Jun-10-92 Minor File path fixes */
/* Version 1.0b Aug-10-92 Minor Bug Fixes */
/* Preprocessor macros */
#define DocClass [[[self class] docClass] class]
#define DocExtension [DocClass extension]
#define theVersion ((float)([[self class] version])/100.0)
#define marginDefault "36.0"
/* Factory (Class) Variables */
/* These are declared as static internal because I felt that all members */
/* of this class would use the same values for these variables. Subclasses */
/* can only manipulate these variables through the provided class methods. */
/* BE VERY CAREFUL about overriding methods which depends on the class */
/* variables without calling the super method - it might cause problems. */
static id docClass; /* Class of Document to use */
static const char *theAppName; /* Cache the appName for later use */
static const char *noValue; /* Cache the local no and yes */
static const char *yesValue;
static const char *VersionFormat; /* Version Format String */
static const int myVersion = 100; /* Version multiplied by 100 */
BOOL InMsgPrint = NO;
/* Factory (Class) Methods */
+ initialize
/* Class variable initialization. DO NOT call from subclasses. */
{
if (self == [MultApp class]) {
[self setVersion:myVersion];
[self setDocClass:[MultDoc class]];
VersionFormat = NXCopyStringBufferFromZone(LocalString("Version %.2f"), MyZone);
theAppName = NXCopyStringBufferFromZone([NXApp appName],MyZone);
noValue = NXCopyStringBufferFromZone(LocalString("NO"),MyZone);
yesValue = NXCopyStringBufferFromZone(LocalString("YES"),MyZone);
}
return self;
}
+ setDocClass:newDoc
/* Sets the document class and the file extensions to be used. Requires */
/* the document class to implement a class method to return the extension */
/* and the file types. */
{
docClass = newDoc;
return self;
}
+ docClass
{
return docClass;
}
/* Private C functions used to implement methods in this class. */
static void initMenu(id menu)
/*
* Sets the updateAction for every menu item which sends to the
* First Responder (i.e. their target is nil). When autoupdate is on,
* every event will be followed by an update of each of the menu items
* which is visible. This keep all unavailable menu items dimmed out
* so that the user knows what options are available at any given time.
*/
{
int count;
id matrix, cell;
id matrixTarget, cellTarget;
matrix = [menu itemList];
matrixTarget = [matrix target];
count = [matrix cellCount];
while (count--) {
cell = [matrix cellAt:count :0];
cellTarget = [cell target];
if (!matrixTarget && !cellTarget) {
[cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
} else if ([cell hasSubmenu]) {
initMenu(cellTarget);
} else if (cellTarget == [NXApp delegate])
[cell setUpdateAction:@selector(menuItemUpdate:) forMenu:menu];
}
}
static id documentInWindow(id window)
/*
* Checks to see if the passed window's delegate is a document.
* If it is, it returns that document, otherwise it returns nil.
*/
{
id document = [window delegate];
if (document) return [document isKindOf:docClass] ? document : nil;
else return nil;
}
static id findDocument(const char *name)
/*
* Searches the window list looking for a document with the specified name.
* Returns the window containing the document if found.
* If name == NULL then the first document found is returned.
*/
{
int count;
id document, window, windowList;
windowList = [NXApp windowList];
count = [windowList count];
while (count--) {
window = [windowList objectAt:count];
document = documentInWindow(window);
if (document && (!name || !strcmp([document fileName], name))) return window;
}
return nil;
}
static id openFile(const char *directory, const char *name, BOOL display)
/*
* Opens a file with the given name in the specified directory.
* If we already have that file open, it is ordered front.
* Returns the document if successful, nil otherwise.
*/
{
id window;
char buffer[MAXPATHLEN+1], path[MAXPATHLEN+1];
if (name && *name) {
if (!directory) directory = ".";
else if (*directory != '/') {
strcpy(buffer, "./");
strcat(buffer, directory);
directory = buffer;
}
if (!chdir(directory) && getwd(path)) {
strcat(path, "/");
strcat(path, name);
window = findDocument(path);
if (window) {
if (display) [window makeKeyAndOrderFront:window];
return [window delegate];
} else return [docClass newFromFile:path];
} else Notify(LocalString("Open: path invalid"), directory);
}
return nil;
}
/* Public Methods */
- currentDocument
{
return documentInWindow([NXApp mainWindow]);
}
- (const char *)currentDirectory
/* Returns the current document's directory */
{
const char *retval = [[self currentDocument] directory];
if (!retval || !*retval) retval = defaultDir;
return retval;
}
- setDefaultDir:(const char *)dir
{
sprintf(defaultDir,"%s",dir);
return self;
}
- (const char *)launchDirectory
{
return launchDir;
}
/* Default support methods */
// These functions manage the app's behavior when it quits with unsaved documents
- (BOOL)saveAll
{
return saveAll;
}
- setSaveAll:(BOOL)value
{
saveAll = value;
return self;
}
- (BOOL)dumpAll
{
return dumpAll;
}
- setDumpAll:(BOOL)value
{
dumpAll = value;
return self;
}
/* Shared Panels */
- info:sender
/*
* Returns the information Panel if it hasn't already been loaded.
*/
{
id versionField; /* version field in info panel */
char buf[20];
if (!infoPanel) {
NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
NXNameZone(zone,"infoPanel");
LoadLocalNib(LocalString("Info.nib"),self,YES,zone);
versionField = NXGetNamedObject("VersionNumber", infoPanel);
if (versionField) {
sprintf(buf, VersionFormat,theVersion);
[versionField setStringValue:buf];
}
NXMergeZone(zone);
}
[infoPanel orderFront:self];
return infoPanel;
}
- pref:sender
/*
* The preferences panel is a separate nib module and only loaded on demand.
* When loaded, the object that manages the panel is asked to update it.
*/
{
if (!prefPanel) {
NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
LoadLocalNib(LocalString("Pref.nib"),self,NO,zone);
NXMergeZone(zone);
}
[[prefPanel delegate] load:self];
[prefPanel orderFront:self];
return prefPanel;
}
- saveAsPanel:sender
{
id savepanel = [SavePanel new];
[savepanel setAccessoryView:nil];
[savepanel setRequiredFileType:DocExtension];
return savepanel;
}
- pageLayout:sender
/*
* Returns the application-wide PageLayout panel, with margins.
*/
{
return pageMargin;
}
- stringSet:sender
/*
* Returns the application-wide string table, if it exists.
*/
{
if (stringSet) return stringSet;
else return nil;
}
- help:sender
{
if (!helpPanel) {
NXZone *zone = NXCreateChildZone(MyZone, vm_page_size, vm_page_size, YES);
LoadLocalNib(LocalString("Help.nib"),self,NO,zone);
NXMergeZone(zone);
}
[[helpPanel delegate] generalHelp:self];
[helpPanel orderFront:self];
return helpPanel;
}
/* Target/Action Methods */
- new:sender
/*
* Called by pressing New in the Document menu.
*/
{
[[docClass class] new];
return self;
}
- open:sender
/*
* Called by pressing Open... in the Document menu.
*/
{
const char *directory;
const char *const *files;
const char *const myType[2] = {DocExtension, NULL};
id openpanel = [[OpenPanel new] allowMultipleFiles:YES];
directory = [self currentDirectory];
if (directory && (*directory == '/')) [openpanel setDirectory:directory];
if ([openpanel runModalForTypes:myType]) {
files = [openpanel filenames];
directory = [openpanel directory];
while (files && *files) {
haveOpenedDocument = openFile(directory, *files, YES) || haveOpenedDocument;
files++;
}
}
return self;
}
- saveAll:sender
/*
* Saves all the documents.
*/
{
int count;
id windowList;
windowList = [NXApp windowList];
count = [windowList count];
while (count--) {
[documentInWindow([windowList objectAt:count]) save:self];
}
return self;
}
- print:sender
{
return [[[self currentDocument] view] printPSCode:sender];
}
- mailToMe:sender
// Stolen from Opener 3.0. Thanks, Michael!
{
char subj[256], w[256] = "whoami";
char body[4096]="\
Subrata:\n\n\
Great source code! It runs like beauty in the night...\n\
BUT! I do have a few comments:\n\n\
<insert accolades, criticisms & suggestions here>\n\n\
Sincerely,\n\
";
#define call(a,b) [s performRemoteMethod:a with:b length:strlen(b)+1]
id s = [NXApp appSpeaker];
port_t mailPort = NXPortFromName("Mail", NULL); // make sure app is launched
mailPort = NXPortFromName("MailSendDemo",NULL);
if (mailPort == PORT_NULL) {
Notify(LocalString("Suggestion attempt failed with Missing Port to Mail"),LocalString("Check if Mail is running."));
return self;
}
[s setSendPort:mailPort];
sprintf(subj,"Comments and suggestions for ``%s'', Version %.2f ",theAppName,theVersion);
strcat(body,execstr(w)); strcat(body,"\n");
call("setTo:","ssircar@canon.com");
call("setSubject:",subj);
call("setBody:",body);
return self;
}
/* Automatic update methods */
- (BOOL)menuItemUpdate:menuCell
/*
* Method called by all menu items which send their actions to the
* First Responder. First, if the object which would respond if the
* action was sent down the responder chain also responds to the message
* validateCommand:, then it is sent validateCommand: to determine
* whether that command is valid now; otherwise, if there is a responder
* to the message, then it is assumed that the item is valid.
* The method returns YES if the cell has changed its appearance (so that
* the caller (a Menu) knows to redraw it).
* This method also traps menu items sending to this class, and sets the
* action the same way.
*/
{
SEL action;
id responder, target;
BOOL enable;
target = [menuCell target];
enable = [menuCell isEnabled];
if (!target) {
action = [menuCell action];
responder = [NXApp calcTargetForAction:action];
if ([responder respondsTo:@selector(validateCommand:)]) {
enable = [responder validateCommand:menuCell];
} else enable = responder ? YES : NO;
} else if (target == self) enable = [self validateCommand:menuCell];
if ([menuCell isEnabled] != enable) {
[menuCell setEnabled:enable];
return YES;
} else return NO;
}
- (BOOL)validateCommand:menuCell
/* The only messages the delegate is asked to validate are SaveAll and Print. */
{
SEL action = [menuCell action];
if ((action == @selector(saveAll:)) || (action == @selector(print:))) return findDocument(NULL) ? YES : NO;
return YES;
}
- setupPageLayout:(float)lm :(float)rm :(float)tm :(float)bm
{
if (!pageMargin) {
pageMargin = [PageMargin new];
[pageMargin setPlpAccessory:plpAccessory];
}
[pageMargin setValues:lm right:rm top:tm bottom:bm];
[pageMargin writePrintInfo];
return self;
}
/* Application Delegate Methods */
- appWillInit:sender
{
char temp[MAXPATHLEN+1];
float lm,rm,tm,bm;
#ifndef DEBUG /* Don't Dump Core if not Debugging */
struct rlimit rl={ 0, 0};
getrlimit( RLIMIT_CORE, &rl);
rl.rlim_cur=0;
setrlimit( RLIMIT_CORE, &rl);
#endif
/* Check and load the application defaults. The basics are the quit */
/* behavior and the page margins. Also sets the menu updating flag. */
if (!NXGetDefaultValue(theAppName,LocalString("SaveAll"))) sprintf(temp,noValue);
else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("SaveAll")));
if (!strcmp(temp,yesValue)) saveAll = YES;
if (!NXGetDefaultValue(theAppName,LocalString("DumpAll"))) sprintf(temp,noValue);
else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("DumpAll")));
if (!strcmp(temp,yesValue)) dumpAll = YES;
if (!NXGetDefaultValue(theAppName,LocalString("LeftMargin"))) sprintf(temp,marginDefault);
else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("LeftMargin")));
sscanf(temp,"%f",&lm);
if (!NXGetDefaultValue(theAppName,LocalString("RightMargin"))) sprintf(temp,marginDefault);
else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("RightMargin")));
sscanf(temp,"%f",&rm);
if (!NXGetDefaultValue(theAppName,LocalString("TopMargin"))) sprintf(temp,marginDefault);
else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("TopMargin")));
sscanf(temp,"%f",&tm);
if (!NXGetDefaultValue(theAppName,LocalString("BottomMargin"))) sprintf(temp,marginDefault);
else sprintf(temp,NXGetDefaultValue(theAppName,LocalString("BottomMargin")));
sscanf(temp,"%f",&bm);
[self setupPageLayout:lm :rm :tm :bm];
[NXApp setAutoupdate:YES];
return self;
}
- appDidInit:sender
/*
* Register our window with the Workspace for icon dropping.
* Check for files to open specified on the command line.
* Initialize the menus and check if we were opened to print or quit.
* If there are no open documents, then open blank documents.
*/
{
int i;
char buffer[MAXPATHLEN+1], temp[MAXPATHLEN+1];
char *directory, *name, *ext;
u_int windowNum;
id speaker;
/* register our app icon window with the workspace */
NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &windowNum);
speaker = [NXApp appSpeaker];
[speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
[speaker registerWindow:windowNum
toPort:[[NXApp appListener] listenPort]];
getAppDirectory(launchDir);
[self setDefaultDir:NXHomeDirectory()];
/* Check for command line files to open */
if (NXArgc > 1) {
for (i = 1; i < NXArgc; i++) {
strcpy(buffer, NXArgv[i]);
ext = rindex(buffer, '.');
if (!ext || strcmp(++ext, DocExtension)) {
strcat(buffer,".");
strcat(buffer, DocExtension);
}
if (*buffer == '/') {
directory = "/";
name = buffer;
name++;
} else {
name = rindex(buffer, '/');
if (name) {
*name++ = '\0';
sprintf(temp,"%s/%s",launchDir,buffer);
directory = temp;
} else {
name = buffer;
directory = NULL;
}
}
haveOpenedDocument = openFile(directory, name, YES) || haveOpenedDocument;
}
}
if (!haveOpenedDocument) [self new:self]; /* if none opened, open one */
if (NXGetDefaultValue(theAppName,LocalString("Quit"))) {
[NXApp activateSelf:YES];
NXPing();
[NXApp terminate:self];
}
initMenu([NXApp mainMenu]);
return self;
}
- appWillTerminate:sender
/*
* Overridden to be sure all documents get an opportunity to be saved
* before exiting the program. Save the defaults too.
*/
{
int count, choice;
float lm,rm,tm,bm;
char temp[100];
id window, document, windowList;
windowList = [NXApp windowList];
count = [windowList count];
[pageMargin getValues:&lm right:&rm top:&tm bottom:&bm];
sprintf(temp,"%f",lm);
NXWriteDefault([NXApp appName],LocalString("LeftMargin"),temp);
sprintf(temp,"%f",rm);
NXWriteDefault([NXApp appName],LocalString("RightMargin"),temp);
sprintf(temp,"%f",tm);
NXWriteDefault([NXApp appName],LocalString("TopMargin"),temp);
sprintf(temp,"%f",bm);
NXWriteDefault([NXApp appName],LocalString("BottomMargin"),temp);
if (saveAll) {
while (count--) {
document = [[windowList objectAt:count] delegate];
if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) {
[document save:self];
}
}
NXWriteDefault(theAppName,LocalString("SaveAll"),yesValue);
NXWriteDefault(theAppName,LocalString("DumpAll"),noValue);
} else if (dumpAll) {
NXWriteDefault(theAppName,LocalString("DumpAll"),yesValue);
NXWriteDefault(theAppName,LocalString("SaveAll"),noValue);
} else {
NXWriteDefault(theAppName,LocalString("DumpAll"),noValue);
NXWriteDefault(theAppName,LocalString("SaveAll"),noValue);
while (count--) {
window = [windowList objectAt:count];
document = [window delegate];
if ([document respondsTo:@selector(needsSaving)] && [document needsSaving]) {
choice = NXRunAlertPanel(LocalString("Quit"),
LocalString("You have unsaved documents."),
LocalString("Review Unsaved"),
LocalString("Quit Anyway"),
LocalString("Cancel"));
if (choice == NX_ALERTOTHER) return nil;
else if (choice == NX_ALERTALTERNATE) return self;
else if (choice == NX_ALERTDEFAULT) {
count = 0;
choice = [windowList count];
while (choice--) {
window = [windowList objectAt:choice];
document = [window delegate];
if ([document respondsTo:@selector(windowWillClose:)]) {
if ([document windowWillClose:self]) [window close];
else return nil;
}
}
}
}
}
}
return self;
}
- (int)appOpenFile:(const char *)path type:(const char *)type
/*
* This method is performed whenever a user double-clicks on an icon
* in the Workspace Manager representing a app program document.
*/
{
char *name;
char directory[MAXPATHLEN+1];
if (type && !strcmp(type, DocExtension)) {
strcpy(directory, path);
name = rindex(directory, '/');
if (name) {
#ifdef DEBUG
printf("Directory %s: File %s\n",directory,name);
#endif
if (name != index(directory, '/')) {
*name++ = '\0';
if (openFile(directory, name, YES)) {
haveOpenedDocument = YES;
return YES;
}
} else {
name++;
if (openFile("/", name, YES)) {
haveOpenedDocument = YES;
return YES;
}
}
#ifdef DEBUG
printf("After Open, File is %s\n",name);
#endif
}
}
return NO;
}
- (BOOL)appAcceptsAnotherFile:sender
/*
* We accept any number of appOpenFile:type: messages.
*/
{
return YES;
}
- (int)app:sender unmounting:(const char *)fullPath
{
id windowList = [NXApp windowList];
int count = [windowList count];
id document = NULL;
/* if any of our documents are in that path, close them and notify the user */
while (count--) {
document = documentInWindow([windowList objectAt:count]);
if (document && (!strncmp([document directory],fullPath,strlen(fullPath)))) {
Notify(fullPath,LocalString("is being unmounted; documents here will close."));
/* Use performClose so that the window delegate will be notified. */
[[document window] performClose:self];
}
}
/* the current directory is wherever our current document is, so we're all set */
return 0;
}
/* Listener/Speaker remote methods */
// IconDragging Stuff, oh boy, oh boy!
static char **filePath = NULL;
static int files = 0;
- (int)iconEntered:(int)windowNum at:(double)x :(double)y
iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY
iconWidth:(double)iconWidth iconHeight:(double)iconHeight
pathList:(char *)pathList
{
char *stringPos, *tempPtr;
char tempBuf[MAXPATHLEN+1];
int count = 0;
if (filePath) {
freeList(filePath);
filePath = NULL;
}
stringPos = tempPtr = pathList;
files = 0;
/* This code just parses the pathList for the filenames and explictly */
/* null-terminates them after copying into a buffer. It appends that */
/* buffer to the dynamically-allocated filelist which keeps track of it */
/* the number of tabs + 1 equals the number of files dragged in */
while (stringPos = index(stringPos, '\t')) {
count = (int)(stringPos-tempPtr);
strncpy(tempBuf,tempPtr,count);
*(tempBuf+count) = '\0';
filePath = addFile(tempBuf,filePath,files,MyZone);
files++;
stringPos++;
tempPtr=stringPos;
}
strncpy(tempBuf,tempPtr,strlen(tempPtr));
*(tempBuf+strlen(tempPtr)) = '\0';
filePath = addFile(tempBuf,filePath,files,MyZone);
files++;
return 0;
}
- (int)iconExitedAt:(double)x :(double)y
// Throw away the files that were just waved in our faces
{
freeList(filePath);
filePath = NULL;
files = 0;
return 0;
}
- (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag
// try to open the files in FileList
{
char *strPtr;
/* accept the icon */
*flag = 1;
NX_DURING
while (files--) {
strPtr = rindex(filePath[files],'/');
if (strPtr != index(filePath[files],'/')) {
*strPtr++ = '\0';
openFile(filePath[files],strPtr,YES);
} else {
strPtr++;
openFile("/",strPtr,YES);
}
}
NX_HANDLER
NX_ENDHANDLER
return 0;
}
// Filename and path methods
- (int)msgDirectory:(const char **)fullPath ok:(int *)flag
{
*fullPath = [self currentDirectory];
if (*fullPath) *flag = YES;
else {
*fullPath = "";
*flag = NO;
}
return 0;
}
- (int)msgFile:(const char **)fullPath ok:(int *)flag
{
const char *file;
file = [[self currentDocument] fileName];
if (file) {
*fullPath = file;
*flag = YES;
} else {
*fullPath = "";
*flag = NO;
}
return 0;
}
// Miscellaneous messages we might want to handle
- (int)msgPrint:(const char *)fullPath ok:(int *)flag
{
BOOL close;
id document = nil;
char *directory, *name;
char path[MAXPATHLEN+1];
char buffer[MAXPATHLEN+1];
InMsgPrint = YES;
strcpy(buffer, fullPath);
name = rindex(buffer, '/');
if (name) {
*name++ = '\0';
directory = buffer;
} else {
name = buffer;
directory = NULL;
}
if (!chdir(directory) && getwd(path)) {
strcat(path, "/");
strcat(path, name);
document = [findDocument(path) delegate];
}
if (document) close = NO;
else {
document = openFile(directory, name, NO);
if (document) haveOpenedDocument = YES;
close = YES;
}
if (document && ![[document view] isEmpty]) {
[NXApp setPrintInfo:[document printInfo]];
[[document view] printPSCode:self];
if (close) [[[document view] window] close];
*flag = YES;
} else *flag = NO;
InMsgPrint = NO;
return 0;
}
- (int)msgVersion:(const char **)aString ok:(int *)flag
{
char buf[20];
sprintf(buf, VersionFormat, theVersion);
*aString = NXCopyStringBuffer(buf);
*flag = YES;
return 0;
}
- (int)msgQuit:(int *)flag
{
*flag = ([NXApp terminate:self] ? NO : YES);
return 0;
}
@end